﻿
function bindFunction(key, targetType, func, baseField) {
    if(!targetType.prototype[key]) {
        targetType.prototype[key] = function() {
            return func.apply(this[baseField], arguments);
        }
    }
}

function bindFunctions(targetType, baseType, baseField) {
    var keys = Object.keys(baseType.prototype);

    for(var i = 0; i < keys.length; i++) {
        var key = keys[i];
        if(baseType.prototype[key] instanceof Function) {
            bindFunction(key, targetType, baseType.prototype[key], baseField);
        }
    }
}

var manager = function() {
    this.commands = {};
};

var command = function(name, manager) {
    this.name = name;
    this.manager = manager;
    this.args = [];
};

var commandArg = function(name, validate, isAsync, command) {
    this.name = name;
    this.cmd = command;
    this.isAsync = isAsync;
    if(validate) {
        if(validate instanceof RegExp) {
            this.validate = module.exports.regex(validate);
        } else if(validate instanceof Function) {
            this.validate = validate;
        } else
            throw "Only functions / regular expressions are allowed for validation method";
    }
};

command.prototype = {
    name: null,
    args: null,
    manager: null,
    _run: null,
    _error: null,

    arg: function(name, validate, isAsync) {
        var result = new commandArg(name, validate, isAsync, this);
        this.args.push(result);
        return result;
    },

    run: function(func) {
        this._run = func;
        return this;
    },

    error: function(func) {
        this._error = func;
        return this;
    },

    execute: function(callback, data, result) {
        var validate = function(flag, i) {
            for(; flag && i < this.args.length; i++) {
                var arg = this.args[i];
                if(!arg.check(function(result) {
                    if(result) i++;
                    validate.call(this, result, i);
                }.bind(this), data)) {
                    flag = false;
                    break;
                }
                if(arg.isAsync)
                    return;
            }
            if(flag) {
                result.code = 0;
                result.msg = "Success";
                if(this._run) {
                    this._run(callback, data, result, this);
                }
            } else {
                result.code = -4;
                result.msg = "Missing or invalid field: `" + this.args[i].name + "`";

                if(this._error)
                    this._error(data, _arg);

                callback();
                return;
            }
        };
        validate.call(this, true, 0);
    },

    use: function(func) {
        return func(this);
    },
};

commandArg.prototype = {
    name: null,
    validate: null,
    command: null,
    isAsync: false,

    check: function(callback, data) {
        if(this.validate) {
            if(this.isAsync) {
                this.validate(callback, data, this);
                return true;
            }
            else
                return this.validate(data, this);
        }
        else
            return true;
    },

    use: function(func) {
        return func(this);
    },
};

manager.prototype = {

    commands: null,


    process: function(req, res) {
        var result = { code: -1, msg: "Internal error", timestamp: new Date().getTime() };

        if(req.body && req.body instanceof Object) {
            var data = req.body;
            var cmd;
            if(data.cmd && (cmd = this.commands[data.cmd])) {
                cmd.execute(function(err) {
                    if(err) {
                        result.code = -1;
                        result.msg = "Internal server error";
                    }
                    res.send(result);
                    res.end();
                }, data, result);
                return;
            } else {
                result.code = -3;
                result.msg = "Missing or invalid command type: `" + data.cmd + "`";
            }
        } else {
            result.code = -2;
            result.msg = "Invalid request";
        }

        res.send(result);
        res.end();
    },

    command: function(name) {
        return this.commands[name] = new command(name, this);
    },

    use: function(func) {
        return func(this);
    },
};

module.exports = manager;

module.exports.bool = function(optional) {
    return function(data, arg) {
        var val = data[arg.name];
        if(!val && optional) return true;
        if(!val || typeof val !== "boolean")
            return false;
        return true;
    }
};

module.exports.int = function(optional) {
    return function(data, arg) {
        var val = data[arg.name];
        if(val === null && optional) return true;
        if(val === null || typeof val !== "number")
            return false;
        return true;
    }
};

module.exports.string = function(optional) {
    return function(data, arg) {
        var val = data[arg.name];
        if(!val && optional) return true;
        if(!val || typeof val !== "string")
            return false;
        return true;
    }
};

module.exports.binary = function(len, optional) {
    return function(data, arg) {
        var val = data[arg.name];
        if(!val && optional) return true;
        if(!val || typeof val !== "string")
            return false;
        try {
            var buf = data[arg.name] = new Buffer(val, 'hex');
            if(buf.length != len)
                return false;
        } catch(TypeError) {
            return false;
        }
        return true;
    };
};

module.exports.date = function(optional) {
    return function(data, arg) {
        var val = data[arg.name];
        if(!val && optional) return true;
        if(!val || typeof val !== "string")
            return false;
        try {
            data[arg.name] = new Date(val);
        } catch(TypeError) {
            return false;
        }
        return true;
    };
};

module.exports.regex = function(reg, optional) {
    return function(data, arg) {
        var val = data[arg.name];
        if(!val && optional) return true;
        if(!val || typeof val !== "string" || !reg.test(val))
            return false;
        return true;
    }
}


bindFunctions(command, manager, "manager");
bindFunctions(commandArg, command, "cmd");
